﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using CustomNetworking;
using BB;
using System.Timers;
using System.IO;

namespace Boggle
{

    class BoggleGame
    {
        private BoggleBoard board;
        private ClientConnection player1, player2;
        private int timeRemaining;
        private Timer gameTimer;
        private static HashSet<string> dictionary;
        private BoggleServer boggleServer;

        private bool gameInProgress = false;

        private readonly object CallbackLock = new object();

        /// <summary>
        /// Creates and starts a new BoggleGame with the specified board layout.
        /// </summary>
        /// <param name="c1">Player 1 connection.</param>
        /// <param name="c2">Player 2 connection.</param>
        /// <param name="time">The duration of the game in seconds.</param>
        /// <param name="dict">The set of legal words.</param> 
        /// <param name="server">The parent BoggleServer object.</param>
        /// <param name="boardString">Optional string representing the board layout.</param>
        public BoggleGame(ClientConnection c1, ClientConnection c2, int time, HashSet<string> dict, BoggleServer server, string boardString = "")
        {
            dictionary = dict;
            player1 = c1;
            player2 = c2;
            timeRemaining = time;

            if (boardString.Length == 16)
                board = new BoggleBoard(boardString);
            else
                board = new BoggleBoard();

            boggleServer = server;
        }

        /// <summary>
        /// Starts the newly-created Boggle Game.
        /// </summary>
        public void StartGame()
        {
            // broadcast the START message to both clients
            string startString = "START " + board.ToString() + " " + timeRemaining + " ";
            player1.SS.BeginSend(startString + player2.Name + "\n", (x, y) => { }, null);
            player2.SS.BeginSend(startString + player1.Name + "\n", (x, y) => { }, null);

            // start listening for messages from the clients
            player1.SS.BeginReceive(BoggleReceiveCallback, player1);
            player2.SS.BeginReceive(BoggleReceiveCallback, player2);

            // start the game timer and mark the game as 'in progress'
            gameTimer = new Timer(1000);
            gameTimer.Elapsed += new ElapsedEventHandler(UpdateTimer);
            gameTimer.Start();
            gameInProgress = true;
        }

        /// <summary>
        /// In-game callback for ClientConnections.
        /// </summary>
        private void BoggleReceiveCallback(string s, Exception e, object p)
        {
            ClientConnection player = (ClientConnection)p;
            try
            {
                lock (CallbackLock)
                {
                    string command = s.ToUpper();
                    command = command.Replace("\r", "");

                    // process valid commands beginning with "WORD"
                    if (command.Substring(0, 5).Equals("WORD ") && gameInProgress)
                    {
                        string word = s.Substring(5);

                        if (board.CanBeFormed(word) && dictionary.Contains(word))
                        {
                            Console.WriteLine("{0} played legal word: {1}", player.Name, word);
                            player.LegalWords.Add(word);
                        }
                        else
                        {
                            Console.WriteLine("{0} played illegal word: {1}", player.Name, word);
                            player.IlegalWords.Add(word);
                        }

                        UpdateScores();
                    }
                    // ignore invalid commands
                    else
                    {
                        player.SS.BeginSend("IGNORING " + s + "\n", (x, y) => { }, null);
                    }

                    // start listening for the next message
                    player.SS.BeginReceive(BoggleReceiveCallback, p);
                }
            }
            catch (Exception)
            {
                // if the game is in progress and one of the clients drops out,
                // stop the game, notify the surviving client and close its connection
                if (gameInProgress)
                {
                    gameInProgress = false;
                    gameTimer.Stop();
                    Console.WriteLine("Lost client connection: {0}", player.Name);

                    // send message to remaining client and close the connection
                    player.opponent.SS.BeginSend("TERMINATED\n", (x, y) => { }, null);
                    player.opponent.SS.CloseSocket(1);
                }

                // once both clients are disconnected, end the game
                if (!player1.SS.IsConnected() & !player2.SS.IsConnected())
                {
                    boggleServer.EndGame(this);
                }
            }
        }

        /// <summary>
        /// Calculates the scores for both players and sends a SCORE message if either
        /// of the scores has changed.
        /// </summary>
        private void UpdateScores()
        {
            // store the previous scores
            int oldScore1 = player1.score;
            int oldScore2 = player2.score;

            // calculate the latest scores
            player1.score = CalculateScore(player1);
            player2.score = CalculateScore(player2);

            // if either of the scores have changed, send the SCORE command to each client
            if (player1.score != oldScore1 | player2.score != oldScore2)
            {
                player1.SS.BeginSend("SCORE " + player1.score + " " + player2.score + "\n", (x, y) => { }, null);
                player2.SS.BeginSend("SCORE " + player2.score + " " + player1.score + "\n", (x, y) => { }, null);
            }
        }

        /// <summary>
        /// Calculates and returns the given player's score.
        /// </summary>
        private int CalculateScore(ClientConnection player)
        {
            HashSet<string> uniqueWords = new HashSet<string>(player.LegalWords);
            uniqueWords.ExceptWith(player.opponent.LegalWords);

            int newScore = 0;
            foreach (string s in uniqueWords)
            {
                if (s.Length > 2 & s.Length < 5) newScore += 1;
                if (s.Length == 5) newScore += 2;
                if (s.Length == 6) newScore += 3;
                if (s.Length == 7) newScore += 5;
                if (s.Length > 7) newScore += 11;
            }

            newScore = newScore - player.IlegalWords.Count;

            return newScore;
        }

        /// <summary>
        /// Generates a string with the post-game summary for the given player.
        /// </summary>
        /// <returns>A post-game summary for the given player.</returns>
        private String GenerateSummary(ClientConnection player)
        {
            // create sets of the player's and opponent's words
            HashSet<string> playerUnique = new HashSet<string>(player.LegalWords);
            HashSet<string> opponentUnique = new HashSet<string>(player.opponent.LegalWords);

            playerUnique.ExceptWith(player.opponent.LegalWords);
            opponentUnique.ExceptWith(player.LegalWords);

            // create a set of the common words
            HashSet<string> common = new HashSet<string>(player.LegalWords);
            common.IntersectWith(player.opponent.LegalWords);

            // create and return the summary message
            string summary = "STOP ";

            summary += playerUnique.Count;
            foreach (string s in playerUnique) summary += " " + s;

            summary += " " + opponentUnique.Count;
            foreach (string s in opponentUnique) summary += " " + s;

            summary += " " + common.Count;
            foreach (string s in common) summary += " " + s;

            summary += " " + player.IlegalWords.Count;
            foreach (string s in player.IlegalWords) summary += " " + s;

            summary += " " + player.opponent.IlegalWords.Count;
            foreach (string s in player.opponent.IlegalWords) summary += " " + s;

            summary += "\n";

            return summary;
        }

        /// <summary>
        /// Event handler for clock tick events.  Counts down the game time, and ends the game when the time reaches zero.
        /// </summary>
        private void UpdateTimer(object source, ElapsedEventArgs e)
        {
            timeRemaining--;

            if (timeRemaining > 0)
            {
                player1.SS.BeginSend("TIME " + timeRemaining.ToString() + "\n", (x, y) => { }, null);
                player2.SS.BeginSend("TIME " + timeRemaining.ToString() + "\n", (x, y) => { }, null);
            }
            else
            {
                // stop the game
                gameInProgress = false;
                gameTimer.Stop();
                UpdateScores();

                // send the ending summaries
                player1.SS.BeginSend(GenerateSummary(player1), (x, y) => { }, null);
                player2.SS.BeginSend(GenerateSummary(player2), (x, y) => { }, null);

                // close the underlying sockets
                player1.SS.CloseSocket(1);
                player2.SS.CloseSocket(1);

                // end the game
                boggleServer.EndGame(this);
            }
        }
    }

}